/*!
gfs client module

# 重点功能函数code
```rust
pub async fn demo_form_upload( State(shared): State<Arc<Gfsclient>>, mut multipart: Multipart) -> AResult<Response, MyError> {
    let mut rets: Vec<GfsResponse> = Vec::new();
    while let Some(field) = multipart.next_field().await.unwrap() {
        let file_name = if let Some(file_name) = field.file_name() {
            file_name.to_owned()
        } else {
            continue;
        };
        if let Ok(ret) = gfsc_stream_to_gfs(&shared.router, &file_name, field).await {
            rets.push(ret);
        }
    }
    save_gfs_response_to_db(&shared.db, dynasty, rets).await?;
    //    ... ...
}
// 文件下载： router.get_download_url(dynasty, idx, ihash, fname.as_str())?;
let dlink = router.get_download_url(dynasty, idx, ihash, fname.as_str())?;
html.push_str(format!("<a href='{}'>download it </a>", dlink).as_str());

// 是否支持文本预览 ：if is_preview_file(ext)
// 在线文本展示 router.get_reader_url(dynasty, id, ihash, filename)?;
let ext: Vec<&str> = file.name.split(".").collect();
if ext.len() > 0 {
    let ext = ext[ext.len() - 1];
    if is_preview_file(ext) {
        let preview_url = router.get_reader_url(router.cur_dynasty, file.id, ihash, file.name.as_str())?;
        builder.push_str(r#"preview <a href=""#);
        builder.push_str(preview_url.as_str());
        builder.push_str(r#"">"#);
        builder.push_str(file.name.as_str());
        builder.push_str(r#" </a><BR>"#);
    }    
}

// 是图片： if is_image_file(ext)
// 在线图片： router.get_image_url(dynasty, id, ihash, filename)?;
let ext: Vec<&str> = file.name.split(".").collect();
if ext.len() > 0 {        
    let ext = ext[ext.len() - 1];
    if is_image_file(ext) {
        let image_url = router.get_image_url(router.cur_dynasty, file.id, ihash, file.name.as_str())?;
        builder.push_str(r#"image online view <a href=""#);
        builder.push_str(image_url.as_str());
        builder.push_str(r#"">"#);
        builder.push_str(file.name.as_str());
        builder.push_str(r#" </a><BR>"#);
    }    
}
// 其他重要函数
gfsc_grep_file(&router, dynasty, pattern).await?;
gfsc_whereis(&router, dynasty, filename ).await?;
gfsc_search(&router, dynasty, &searchParams).await?;
gfsc_dir(&router, dynasty, ihash, idx, limit).await?;
gfsc_get_files(&router, dynasty, 0, 10).await?; 
gfsc_read_text(&router, dynasty, idx, ihash, &save_name)?;

```
# code examples

cargo build --example search
## examples as:
- [dir.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/dir.rs)  
- [download.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/download.rs)    
- [grep.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/grep.rs)    
- [readline.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/readline.rs)    
- [router.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/router.rs)    
- [search.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/search.rs)    
- [sendfile.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/sendfile.rs)    
- [whereis.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/whereis.rs)  
- [分布式计算](https://gitee.com/lenssh/rabbit/blob/master/gfsc/examples/parallel.rs)  

cargo build --bin hare
## App as:
- [demo.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/src/bin/demo.rs)  
- [hare.rs](https://gitee.com/lenssh/rabbit/blob/master/gfsc/src/bin/hare.rs)  

cargo test sendfile_file_ok --test sendfile

 */

use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::path::PathBuf;
use tokio;
use md5::{Digest, Md5};


#[cfg(feature = "tracelog")]
use tracing::{debug, error, info};
#[cfg(feature = "rotatelog")]
use log::{debug, error, info, warn};

mod router;
pub use router::*;
mod error;
pub use error::*;
mod gfsc;
pub use gfsc::*;
mod search;
pub use search::*;
mod define;
pub use define::*;
mod parallel;
pub use parallel::*;

type Result<T> = anyhow::Result<T>;
const AUTH_TOKEN: &str = "hello";
const STREAM_WRITE_TO_GFS:i32 = 0;
pub const TOKEN_TIMEOUT: u64 = 60;

/**
REST请求中获取limit个Stat数据的参数结构
 */
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[allow(dead_code)]
pub struct GfsParams {
    #[serde(default = "default_string")]
    pub token: String,
    #[serde(default = "default_number")]    
    pub from: i32,
    #[serde(default = "default_u32")]    
    pub limit: u32,
}


/**
REST请求中，进行文件读写操作的参数结构
 */
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FileParam {
    #[serde(default = "default_string")]
    pub name: String,
    #[serde(default = "default_string")]
    pub token: String,
    #[serde(default = "default_number")]
    pub ihash: i32,
    #[serde(default = "default_number")]    
    pub idx: i32,
    #[serde(default = "default_number")]
    pub dynasty: i32,
    #[serde(default = "default_u32")]
    pub limit: u32,
}

/**
Record类型文件记录的参数结构
 */  
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct RecordParam {
    /// record file 名称
    #[serde(default = "default_string")]
    pub name: String,
    /// 如果用于算子计算记录，则为算子名称，否则忽略
    #[serde(default = "default_string")]
    pub taskname: String,
    #[serde(default = "default_string")]
    pub token: String,
    ///算子在gfs上的ihash
    #[serde(default = "default_number")]
    pub ihash: i32,
    ///算子在gfs上的idx
    #[serde(default = "default_number")]    
    pub idx: i32,
    /// 算子记录中对应的计算目标ihash值，否则忽略
    #[serde(default = "default_number")]
    pub ikey: i32,    
}

/**
REST请求中， 返回的文件定位信息结构
 */
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct GfsResponse {
    #[serde(default = "default_string")]
    pub name: String,
    #[serde(default = "default_number")]
    pub ihash: i32,
    #[serde(default = "default_number")]
    pub idx: i32,
    #[serde(default = "default_number")]
    pub code: i32,
}
fn default_string() -> String {
    "".to_string()
}
fn default_number() -> i32 {
    0
}
fn default_usize() -> usize {
    0
}
fn default_u32() -> u32 {
    0
}


/**
REST请求中，返回的文件定位信息stat表数据结构
 */
#[derive(Debug, Clone, Serialize, Deserialize)]
// #[sqlx(rename_all = "PascalCase")]
pub struct Stat {
    pub id: i32,
    pub name: String,
    pub fhash: String,
    //  ctime: chrono::NaiveDateTime,
    status: u8,
}

/**
文件名hash为0~255的i32，注意：已经256取模处理。
 * 
 */
pub fn gfs_filename_hash(fname: &str) -> i32 {
    let fullname = PathBuf::from(fname); 
    // 去掉路径，保留纯文件名！ 
    let file_name = fullname.file_name().unwrap_or_default().to_string_lossy(); // .to_str().unwrap_or("error");
    return string_hash256(&file_name)
}

/**
 string to hash256 (0~255的i32)
 * 这里采用的是Md5，而不是sha256, 因为能耗更低，质量不影响结果分布。
 */
pub fn string_hash256(txt: &str ) -> i32 {
    let mut hasher = Md5::new();
    hasher.update(txt.as_bytes());
    let result = hasher.finalize();
    let sum: i32 = result[..].iter().map(|&x| x as i32).sum();
    return sum % 256;
}

/**
文件名有效性检验，这里的文件名必须是文件路径中最后一个元素
 * 有助于安全，防止路径合法性攻击
 * 有助于文件名hash的一致性
 */
pub fn filename_is_valid(path: &str) -> bool {
    let path = std::path::Path::new(path);
    let mut components = path.components().peekable();
    if let Some(first) = components.peek() {
        if !matches!(first, std::path::Component::Normal(_)) {
            return false;
        }
    }
    components.count() == 1
}

/**
生成有效性链接的token串
 * 借助verify_token(token:&str, timeout: u64) -> bool 进行验证
 */
pub fn new_token() -> String {    
    let t = std::time::SystemTime::now()
    .duration_since(std::time::UNIX_EPOCH)
    .unwrap_or(std::time::Duration::new(0, 0))
    .as_secs();
    
    let mut hasher = Md5::new();
    hasher.update( format!("{}{}",AUTH_TOKEN, t).as_bytes());
    let result = hasher.finalize();
    format!("{}|{}", hex::encode(result), t)
}

/**
token有效性校验
 * timeout表示允许token存活的有效时长（秒）       
 * 通过new_token()-> String 生成token
 */
pub fn verify_token(token:&str, timeout: u64) -> bool {    
    
    let sv:Vec<&str> = token.split('|').collect();
    if sv.len() != 2 {
        return false
    }

    let old_t = sv[1].parse::<u64>().unwrap_or_default();
    debug!("half_token={}, time-t={}", &sv[0], &sv[1]);    
    let mut hasher = Md5::new();
    hasher.update( format!("{}{}",AUTH_TOKEN, old_t).as_bytes());
    let result = hasher.finalize();
    let new_half_token = format!("{}", hex::encode(result));

    let t = std::time::SystemTime::now()
    .duration_since(std::time::UNIX_EPOCH)
    .unwrap_or(std::time::Duration::new(0, 0))
    .as_secs();

    debug!("half_token={}, time-t={}", &new_half_token, t);    
    if new_half_token.eq(&sv[0].to_string()) && t - old_t <= timeout {
        return true
    } 
    false
}


/**
 * 把gfs系统的数字ihash, idx构造为目标目录，数字将被转换为16进制字符。
 * ihash将被取模256,限制在ff以内。
 */
pub fn gfs_hashcode_to_path(parent:&PathBuf, ihash:i32, idx:i32) -> PathBuf  {
    let workpath = std::path::Path::new(parent)
    .join(format!("{:02x}", ihash%256))
    .join(format!("{:02x}", idx));
    return workpath
}

///根据位置3元素生成gfs的精确存储路径文件名（SearchDoc中的fname）
pub fn gfs_hashcode_filename(ihash:i32, idx:i32, fname:&str) -> String  {
    format!("{:02x}/{:02x}/{}", ihash%256, idx, fname)
}

/**
 * Stat结构 转换成 GfsResponse结构数据。
 * 
 */
 
pub fn gfs_stat_as_response(stat :&Stat )->GfsResponse {    
    let ihash = if let Ok(a) = hex::decode(stat.fhash.as_str()){
        a[0] as i32
    }else{
        gfs_filename_hash(stat.name.as_str())
    };    
    GfsResponse { name: stat.name.clone(), ihash, idx:stat.id, code:0}
}
/// 二进制的下载路径，http://ip:port/download?ihash={}&idx={}&name={}&token={} 
pub fn get_download_url(server:&str, idx: i32, ihash:i32, fname: &str) -> String {        
    format!("{}/download?ihash={}&idx={}&name={}&token={}", server, ihash%256, idx, urlencoding::encode(fname), new_token())
}

/**
文本格式的读取路径，http://ip:port/reader?ihash={}&idx={}&name={}&token={} 
 * 是否支持文本预览 ：if is_preview_file(ext)
*/
pub fn get_reader_url(server:&str, idx: i32, ihash:i32, fname: &str) -> String {        
    format!("{}/reader?ihash={}&idx={}&name={}&token={}", server, ihash%256, idx, urlencoding::encode(fname), new_token())
}

/**
根据搜索结果SearchDoc的fname字段（字段结构指定为fhash/idx/name），返回 gfs中文件的3元定位信息
 * 对于错误的fname，函数出于简单原因，做了错误吞噬处理，返回不存在的（0,0,fname)3元信息
 */
pub fn get_element_by_search_fname(fname:&str)->(i32, i32, String) {
    let fvs:Vec<&str> = fname.split("/").collect();
    if fvs.len() == 3 {
        let ihash = hex::decode(fvs[0]).unwrap_or_default()[0] as i32;
        let idx = hex::decode(fvs[1]).unwrap_or_default()[0] as i32;        
        return (ihash, idx, fvs[2].to_string())
    }
    (0,0, fname.to_string())
}
 
/* 
#[test]
fn hash_file() {
    let path = "README.md";
    let i = gfs_filename_hash(path);
    println!("{:x}", i);

    for i in 11024..11056 {
        println!("{:02x}", i);
    }
}

#[test]
fn send_file_test() {
    let fname = "/home/lengss/rust/abc.jpeg";
    let f = send_file(fname);
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(f);
}
#[test]
fn copy_file_test() {   
    let parent = PathBuf::from("./uploads");
    let f = gfs_copy_from_master(&parent,"abc.jpeg",231, 1);
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(f);
}

#[test]
fn read_stat_test() {   
   
    let f = gfs_read_stat("http://localhost:9996", 0, 10);
    let rt = tokio::runtime::Runtime::new().unwrap();
    let r = rt.block_on(f);
    println!("{:#?}",r);

}
 */


 #[test]
 fn filename_only_fname_test() {
 
     let src = vec!["/home/lengss/abc.jpeg","./examples/hello abc.json","./ab.txt","abc.docx"];
 
     for s in src {
         let fullname = PathBuf::from(s); 
         // 去掉路径，保留纯文件名！ 
         let file_name = fullname.file_name().unwrap_or_default().to_string_lossy(); 
         println!("souce is : {:#?}", s );
         println!("only file name is : {:#?}", file_name );
     }  
 
 }


#[test]
fn token_verify_test() {
    let token = new_token();
    println!("new token is : {}", &token);

    let is_ok = verify_token(token.as_str(), 10);
    println!("{}", is_ok);
    std::thread::sleep(std::time::Duration::from_secs(12));
    let is_ok = verify_token(token.as_str(), 10);
    println!("{}", is_ok);

}